import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.io.*;

/** <br><br>
 * Change Log:<Br>
 *  - 04/12/09 - Dan Carpenter<br>
 *  	- Added methods: symbolIsDefined(), updateLocation(), updateUsage(), getLocation()<br><br>
 *  - 04/13/09 - Meghan Day<br>
 *  	- Changed every occurrence of "Assembler.USAGE" to Assembler.SymbolUsage so
 *  	  that the class is compilable.<br><br>
 *  - 04/13/09 - Tobin Chee<br>
 *  	- Added method signature for defineSymbol() so that Assembler won't throw errors.<br><br>
 *  - 5/10/09 - Dan Carpenter<br>
 *  	- Added method getNamesOfUsage
 *  
 *  @author Tobin Chee
 */
public class SymbolTable {
	/** ArrayList representation of label of each symbol added to table.
	 */
	private List<String> symbolsLabel = new ArrayList<String>();
	/** ArrayList representation of memory location of each symbol added to table, if memory location is associated with symbol.
	 */
	private List<Integer> symbolsHexLocation = new ArrayList<Integer>();
	/** ArrayList representation of type of symbol in table
	 */
	private List<Assembler.SymbolUsage> symbolsUsage = new ArrayList<Assembler.SymbolUsage>();
	/** ArrayList representation of equated symbol value for equated symbols.
	 */
	private List<Integer> symbolsEQUValue = new ArrayList<Integer>();

	/** Construct an empty symbol table
	 */
	public SymbolTable(){
	}
	
	/**
	 * Calculates and outputs the correct number of tabs based on the width of a formatted column (width in tabs)
	 * and the length of the string output in the column.
	 * 
	 * @param reportFile the file this method writes to.
	 * @param columnWidth the width of a formatted column in tabs.
	 * @param stringLength the length of the string output in the column.
	 */
	private void putTabsToFile(BufferedWriter reportFile, Integer columnWidth, Integer stringLength) throws IOException{
		
			final int tabWidth = 6;
			int tabs = ((columnWidth * tabWidth) - stringLength) / tabWidth;
			if((stringLength % tabWidth) != 0){
				tabs++;
			}
			for(int j = 0; j < tabs; j++){
				reportFile.write("\t");
			}
	}
	
	/** 
	 * Outputs symbol table to report file formatted as defined in Appendix E of the lab manual.
	 * 
	 * @param reportFile the file this method writes to.
	 * @author Tobin Chee
	 * 
	 * Change Log:
	 * 	- 06/08/09 - Tobin Chee
	 * 		- Changed formatting of output to match Appendix E of the lab manual.
	 */
	public void putToFile(BufferedWriter reportFile){
		try{
			//header
			reportFile.write("Label\t\t\t\t\taddress\tsubstitution\tusage\n");
			for(int i = 0; i < symbolsLabel.size(); i++){
				String temp = "";
				//Label
				temp = symbolsLabel.get(i);
				reportFile.write(temp);
				putTabsToFile(reportFile, 5, temp.length());
				
				//address
				if(symbolsHexLocation.get(i) >= 0){
					temp = Integer.toHexString(symbolsHexLocation.get(i));
				}
				else{
					temp = "--";
				}
				reportFile.write(temp);
				putTabsToFile(reportFile, 2, symbolsHexLocation.get(i).toString().length());
				
				//substitution
				if(symbolsUsage.get(i) == Assembler.SymbolUsage.EQU_VALUE){
					temp = Integer.toHexString(symbolsEQUValue.get(i));
				}
				else{
					temp = "none";
				}
				reportFile.write(temp);
				putTabsToFile(reportFile, 3, temp.length());
				
				//usage
				reportFile.write(symbolsUsage.get(i).toString() + "\n");
			}
				
			} catch(IOException ioe){
				System.out.println("ERROR: Problem writing symbol table to user report file");
			}
			
			/*old code
			reportFile.write("Symbol Table\n");
			reportFile.write("________________________________\n");
			for(int i = 0; i < symbolsLabel.size(); i++){
				reportFile.write("Label: " + symbolsLabel.get(i) + "\n");
				reportFile.write("Location (Dec): ");
				if(symbolsHexLocation.get(i) >= 0){
					reportFile.write(symbolsHexLocation.get(i).toString() + "\n");
				}
				else{
					reportFile.write("No location\n");
				}
				reportFile.write("Usage: " + symbolsUsage.get(i) + "\n");
				reportFile.write("EQU value: ");
				if(symbolsUsage.get(i) == Assembler.SymbolUsage.EQU_VALUE){
					reportFile.write(symbolsEQUValue.get(i).toString());
				}
				else{
					reportFile.write("No value");
				}
				reportFile.write("\n\n");
			}
		} catch(IOException ioe){
			System.out.println("Assembler error: SymbolTable.putToFile");
		}
		*/
		
	}
	
	/** 
	 * Sorts the symbol table in alphabetical order by label.
	 * 
	 * @author Tobin Chee
	 * 
	 * Change Log:
	 * 	- No changes
	 */
	public void sort(){
		List<String> labels = new ArrayList<String>(symbolsLabel);
		List<Integer> hexLocations = new ArrayList<Integer>(symbolsHexLocation);
		symbolsHexLocation.clear();
		List<Assembler.SymbolUsage> usages = new ArrayList<Assembler.SymbolUsage>(symbolsUsage);
		symbolsUsage.clear();
		List<Integer> EQUValues = new ArrayList<Integer>(symbolsEQUValue);
		symbolsEQUValue.clear();
		
		Collections.sort(labels);
		
		for(String lbl: labels){
			int index = symbolsLabel.indexOf(lbl);
			
			symbolsLabel.remove(index);
			symbolsHexLocation.add(hexLocations.remove(index));
			symbolsUsage.add(usages.remove(index));
			symbolsEQUValue.add(EQUValues.remove(index));
		}
		
		symbolsLabel = new ArrayList<String>(labels); //dont' need to clear labels
	}
	
	/** Define a new symbol in the symbol table.<br><br>
	 * 
	 * REQUIRES: Label is not already defined in the symbol table.
	 * @param symb Symbol to define.
	 */
	public void defineSymbol(Symbol symb){
		if(symbolIsDefined(symb.getLabel())){
			int index = symbolsLabel.indexOf(symb.getLabel());
			
			symbolsLabel.set(index, symb.getLabel());
			symbolsHexLocation.set(index, symb.getLocation());
			symbolsUsage.set(index, symb.getUsage());
			symbolsEQUValue.set(index, symb.getEQUValue());
		}
		else{ //if not defined
			symbolsLabel.add(symb.getLabel());
			symbolsHexLocation.add(symb.getLocation());
			symbolsUsage.add(symb.getUsage());
			symbolsEQUValue.add(symb.getEQUValue());
		}
		
	}
	
	/**
	 * TODO document
	 * @param label
	 * @param EQUVal
	 */
	public void updateEQUValue(String label, Integer EQUValue){
		int index = symbolsLabel.indexOf(label);
		symbolsEQUValue.set(index, EQUValue);
	}
	
	/** Find the usage of a symbol.<br><br>
	 * Requires: symbolIsDefined(label) -> Label is in symbol table.
	 * 
	 * @param label Label to look up.
	 * @return The usage of the symbol provided requires clause is met.
	 */
	public Assembler.SymbolUsage getUsage(String label){
		int index = symbolsLabel.indexOf(label);
		
		return symbolsUsage.get(index);
	}
	
	/** See if a symbol has been defined in the symbol table. 
	 * 
	 * @param label Symbol to look up.
	 * @return True if symbol is defined, otherwise return false.
	 */
	public boolean symbolIsDefined (String label) {
		return symbolsLabel.contains(label);
	}

	/** Update the memory location of a symbol.
	 * Requires symbolIsDefined(label).
	 * @param label Symbol in table to update.
	 * @param location Memory location to update for symbol.
	 */
	public void UpdateLocation (String label, Integer location ) {
		int index = symbolsLabel.indexOf(label);

		symbolsHexLocation.set(index, location);
	}
	
	/** Update the usage of a symbol in the symbol table. 
	 * Requires symbolIsDefined(label).
	 * @param label Symbol in table to update.
	 * @param usage Usage of symbol to be updated.
	 */
	public void UpdateUsage (String label, Assembler.SymbolUsage usage) {
		int index = symbolsLabel.indexOf(label);
		
		symbolsUsage.set(index, usage);
	}
	
	/** Find the location of a symbol in the symbol table.<br><br>
	 * Requires symbolIsDefined(label).
	 * Change Log:
	 * - 5/11 Sam Jones
	 * - Created for use in putHeaderRecord method of Assembler
	 * 
	 * @param label Symbol in table to find location of.
	 * @return Location of symbol.
	 */
	public int getLocation (String label){
		int index = symbolsLabel.indexOf(label);
		
		return symbolsHexLocation.get(index);
	}
	
	/** Find the equated symbol value of a symbol.<br><br>
	 * Requires symbolIsDefined(label).
	 * @param label Symbol in table to find equated symbol value of.
	 * @return Equated symbol value of symbol.
	 */
	public int getEQUValue(String label){
		int index = symbolsLabel.indexOf(label);
		
		return symbolsEQUValue.get(index);
	}
	
	/** Find the length of a symbol in the symbol table.
	 * Requires symbolIsDefined(label).
	 * @param label Symbol in table to find the length of.
	 * @return Length of symbol.
	 */
		public Integer length () {
		return symbolsLabel.size();
	}
	
	/**
	 * TODO document	
	 * @param label
	 */
	public void removeSymbol(String label){
		int index = symbolsLabel.indexOf(label);
		
		symbolsLabel.remove(index);
		symbolsEQUValue.remove(index);
		symbolsHexLocation.remove(index);
		symbolsUsage.remove(index);
	}
		
	/** Determine if the symbol table contains a parameter list.
	 * @return True if a parameter list has been defined, otherwise return false.
	 */
	public boolean containsPRMID(){
		return symbolsUsage.contains(Assembler.SymbolUsage.PRMID);
	}
	
	/** Find the label of the parameter list in the symbol table.
	 * Requires SymbolTable.containsPRMID().
	 * 
	 * @return Label of the parameter list in the symbol.
	 */
	public String getPRMIDLabel(){
		int index = symbolsUsage.indexOf(Assembler.SymbolUsage.PRMID);
		
		return symbolsLabel.get(index);
		
	}
	/**Returns the name of the program being assembled.
	 * 
	 * @return Name of the program being assembled.
	 */
	public String getProgramName(){
		int index = symbolsUsage.indexOf(Assembler.SymbolUsage.PROGRAM_NAME);
		return symbolsLabel.get(index);
	}

	/**
	 * Finds the names of all labels of a given usage.
	 * 
	 * @param use
	 * @return names of all labels of the given type
	 */
	public String[] getNamesOfUsage(Assembler.SymbolUsage use) {
		String[] names = new String[symbolsLabel.size()];
		int count = 0, index = 0;
		
		while(count < symbolsLabel.size()) {
			if(symbolsUsage.get(count) == use) {
				names[index] = symbolsLabel.get(count);
				index++;
			}
			count++;
		}
		
		return names;
	}

	/** Find the usage of a symbol in a particular row of the symbol table.
	 * @param symbolIndex Row of symbol table to find usage for.
	 * @return Usage of symbol at specified row.
	 */
	public Assembler.SymbolUsage getUsage(int symbolIndex) {
		return symbolsUsage.get(symbolIndex);
	}
	
	/**
	 * Returns the label of the symbol at the given index.
	 * @param symbolIndex the table index of the desired label.
	 * @return the String label of the symbol at symbolIndex.
	 */
	public String getSymbolLabel(int symbolIndex){
		return symbolsLabel.get(symbolIndex);
	}
}

